from enum import Enum, unique
from math import ceil
from time import strftime, strptime, mktime, localtime
from typing import Dict, List, Tuple
from random import randint
import math
import bisect

VEHICLE_NAMES = ("摆渡车", "加油车", "清水车", "行李车", "食品车", "清洁车", "拖车")

@unique
class VehicleType(Enum):
    ShuttleBus = 0 #摆渡车
    GasRefueller = 1 #加油车
    ClearwaterCar = 2 #清水车
    BaggageTruck = 3 #行李车
    FoodCar = 4 #食品车
    CleanerCar = 5 #清洁车
    Trailer = 6 #拖车

    @classmethod
    def get_pre_service_car_type(cls, type) -> List:
        if type == VehicleType.CleanerCar or type == VehicleType.FoodCar:
            return [VehicleType.ShuttleBus]
        if type == VehicleType.Trailer:
            return [type for type in VehicleType if type != VehicleType.Trailer]
        return []

@unique
class VehicleState(Enum):
    Standby = 0 #车在车库待命
    Coming = 1 #车正在赶往某个停机位
    Waiting = 2 #车已到达停车位，正等待服务
    Serving = 3 #服务中
    Complete = 4 #完成
    Backing = 5 #正在回到指定停岗点
    Recovering = 6 #休整钟    

class VehicleProperty:
    def __init__(self, speed, service_time_cost, cap, cost, recover_time_cost) -> None:
        self.speed:float = speed
        self.service_time_cost:int = service_time_cost
        self.capcity:int = cap
        self.cost:float = cost
        self.recover_time_cost:int = recover_time_cost
    
    def __str__(self) -> str:
        return f"速度：{self.speed} \n服务耗时：{self.service_time_cost} \n可服务次数：{self.capcity} \n休整耗时：{self.recover_time_cost} \n代价：{self.cost}"

class Vehicle:
    #(速度, 服务时长, 可服务次数, 花费)
    property_of_type: Dict[VehicleType, VehicleProperty] = {}

    def __init__(self, type:VehicleType, index:int, park_index:int) -> None:
        self.speed:float = Vehicle.property_of_type[type].speed 
        self.state:VehicleState = VehicleState.Standby
        self.index:int = index
        self.park_index:int = park_index
        self.type:VehicleType = type
        self.path:Path = None
        self.back_path:Path = None
        self.capcity:int = Vehicle.property_of_type[type].capcity
        self.gate_index:int = 0
        self.gate_name:str = ""
        self.time_stamp:int = 0
        self.end_time:int = 0
        self.start_time:int = 0
        self.time_cost:int = 1

    def refresh(self):
        self.state = VehicleState.Standby
        self.capcity = Vehicle.property_of_type[self.type].capcity
        self.speed = Vehicle.property_of_type[self.type].speed 
        self.gate_index = 0
        self.gate_name = ""
        self.time_stamp = 0
        self.end_time = 0
        self.start_time = 0
        self.time_cost = 1
    
    def get_progress_value(self) -> float:
        if self.time_cost == 0:
            return 0
        return (self.time_stamp - self.start_time) /self.time_cost

    def time_forward(self, time_stamp:int):
        if self.time_stamp == time_stamp:
            return
        
        self.time_stamp = time_stamp

        if time_stamp < self.end_time:
            return

        if self.state == VehicleState.Serving:
            self.state = VehicleState.Complete
            self.capcity -= 1
            if self.capcity == 0:
                self.state = VehicleState.Backing
                self.path = self.back_path
                self.time_cost = ceil(self.back_path.length / self.speed)
                self.start_time = time_stamp
                self.end_time = time_stamp + self.time_cost
            return
        
        if self.state == VehicleState.Coming:
            self.state = VehicleState.Waiting
            return
        
        if self.state == VehicleState.Backing:
            self.state = VehicleState.Recovering
            self.time_cost = Vehicle.property_of_type[self.type].recover_time_cost
            self.start_time = time_stamp
            self.end_time = time_stamp + self.time_cost
            return
        
        if self.state == VehicleState.Recovering:
            self.state = VehicleState.Standby
            self.capcity = Vehicle.property_of_type[self.type].capcity
            return

    @classmethod
    def generate_random_vehicle_property(cls):
        for vtype in VehicleType:
            speed = randint(1, 3)
            cap = randint(1, 2)
            service_time_cost = randint(1, 3) * 4 * 60
            cost = 20 + vtype.value * 5
            rec = randint(5, 10) * 60
            cls.property_of_type[vtype] = VehicleProperty(speed, service_time_cost, cap, cost, rec)
    
    def __str__(self) -> str:
        return f"机场:{self.gate_name} 状态:{self.state} 编号:{self.index} 类别:{self.type.name} 进度: {self.get_progress_value()}"

class Gate():
    def __init__(self, name:str, pos_index:int, index:int, pos:Tuple[float, float], is_near_gate:bool) -> None:
        self.vehicles:List[Vehicle] = [None for _ in VehicleType]
        self.next_vehicles:List[Vehicle] = [None for _ in VehicleType]
        self.vehicles_work_done:List[bool] = [False for _ in VehicleType]
        self.name:str = name
        self.pos_index:int = pos_index
        self.pos:Tuple[float, float] = pos
        self.index:int = index
        self.start_serving_time:int = 0
        self.flight:Flight = None
        self.is_working:bool = False
        self.is_near_gate:bool = is_near_gate
        if is_near_gate:
            self.vehicles_work_done[VehicleType.ShuttleBus.value] = True
    
    def refresh(self):
        self.vehicles:List[Vehicle] = [None for t in VehicleType]
        self.next_vehicles:List[Vehicle] = [None for t in VehicleType]
        self.vehicles_work_done = [False for t in VehicleType]
        self.start_serving_time = 0
        self.flight:Flight = None
        self.is_working = False  
        if self.is_near_gate:
            self.vehicles_work_done[VehicleType.ShuttleBus.value] = True
    
    def time_forward(self, time_stamp:int):
        if not self.flight.is_arrived:
            return

        serving_vehicles = list(filter(lambda v: isinstance(v, Vehicle) and v.state == VehicleState.Serving, self.vehicles))
        for vehicle in serving_vehicles:
            vehicle.time_forward(time_stamp)
            if vehicle.state == VehicleState.Complete or vehicle.state == VehicleState.Backing:
                vehicle.gate_index = -1
                vehicle.gate_name = ""
                self.vehicles_work_done[vehicle.type.value] = True
                self.vehicles[vehicle.type.value] = None
        
        waiting_vehicles = list(filter(lambda v: isinstance(v, Vehicle) and v.state == VehicleState.Waiting, self.vehicles))
        for vehicle in waiting_vehicles:
            can_serving = True
            for vtype in VehicleType.get_pre_service_car_type(vehicle.type):
                if not self.vehicles_work_done[vtype.value]:
                    can_serving = False
                    break
            if can_serving:
                vehicle.state = VehicleState.Serving
                vehicle.start_time = time_stamp
                vehicle.time_cost = Vehicle.property_of_type[vehicle.type].service_time_cost
                vehicle.end_time = time_stamp + vehicle.time_cost
        

class Park():
    def __init__(self, name:str, pos_index:int, index:int, pos:Tuple[float, float], vehicles:List[List[Vehicle]]) -> None:
        self.name:str = name
        self.pos_index:int = pos_index
        self.pos:Tuple[float, float] = pos
        self.index:int = index
        self.vehicles:List[List[Vehicle]] = vehicles

class Flight():
    def __init__(self, name, arrival_time, gate_name, gate_index) -> None:
        self.name:str = name
        self.arrival_time:str = arrival_time
        self.arrival_time_stamp:int = Clock.get_time_stamp(arrival_time)
        self.gate_name:str = gate_name
        self.gate_index:int = gate_index
        self.is_arrived:bool = False
        self.is_done:bool = False
    
    def refresh(self):
        self.is_arrived = False
        self.is_done = False

class Path():
    def __init__(self, points, discription:str) -> None:
        self.points:List[Tuple[float, float]] = points
        self.description:str = discription
        self.distances:List[float] = [Path.get_distance(points[i], points[i+1]) for i in range(len(points) - 1)]
        
        self.distances_pre_sum:List[float] = []
        tmp = 0
        for distance in self.distances:
            tmp += distance
            self.distances_pre_sum.append(tmp)

        self.angles:list[float] = []
        for i in range(len(points) - 1):
            x1, y1 = points[i]
            x2, y2 = points[i + 1]
            if x1 - x2 == 0:
                angle = 270 if y1 > y2 else 90
            elif y1 - y2 == 0:
                angle = 180 if x1 > x2 else 0
            else:                
                k = (y1 - y2) / (x1 - x2)
                angle = math.atan(k) / math.pi * 180
                if angle < 0:
                    angle += 180

                if x1 < x2 and y1 > y2 or x1 > x2 and y1 > y2:
                    angle += 180

            self.angles.append(angle)

        self.length:float = sum(self.distances)

    def get_point_of_progress(self, progress_value:float) -> Tuple[Tuple[float, float], float]:
        length = self.length * progress_value
        start = bisect.bisect_left(self.distances_pre_sum, length)

        length = length if start - 1 < 0 else length - self.distances_pre_sum[start - 1]
        p = length / self.distances[start]
        x = (self.points[start+1][0] - self.points[start][0]) * p + self.points[start][0]
        y = (self.points[start+1][1] - self.points[start][1]) * p + self.points[start][1]

        return (x, y) , self.angles[start]
    
    @classmethod
    def get_distance(cls, first_point, second_point) -> float:
        return ((first_point[0] - second_point[0])**2 + (first_point[1] - second_point[1])**2) ** 0.5

        
class Clock():
    def __init__(self) -> None:
        self.time:int = 0

    def set_time(self, time_str:str, ahead_min:int = 0, ahead_sec:int = 0):
        self.time = Clock.get_time_stamp(time_str, ahead_min, ahead_sec)

    def set_time_stamp(self, time_stamp):
        self.time = time_stamp

    def time_forward(self):
        self.time += 1
    
    def get_date(self) -> str:
        return strftime("%Y年%m月%d日", localtime(self.time))
    
    def get_full_time(self) -> str:
        return strftime("%Y-%m-%d %H:%M", localtime(self.time))

    def get_time(self) -> str:
        return strftime("%H:%M:%S", localtime(self.time))

    @classmethod
    def get_time_stamp(cls, time_str:str, aheadMin:int = 0, aheadSec:int = 0):
        return mktime(strptime(time_str, "%Y-%m-%d %H:%M")) - aheadMin * 60 - aheadSec
    
    @classmethod
    def get_time_str_with_HMS(cls, time):
        return strftime("%H:%M:%S", localtime(time))

    @classmethod
    def get_time_str(cls, time):
        return strftime("%Y年%m月%d日 %H:%M", localtime(time))
